package sisicotren.ui.windows;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;

import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

import java.awt.GridBagLayout;
import javax.swing.JLabel;
import java.awt.GridBagConstraints;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Properties;

import javax.swing.JButton;

import sisicotren.main.EngineThread;
import sisicotren.model.simulator.Channel;
import sisicotren.model.simulator.ClientChannelDispatcher;
import sisicotren.model.simulator.ClientsInjector;
import sisicotren.model.simulator.Engine;
import sisicotren.model.simulator.Queue;
import sisicotren.model.simulator.SimulationStats;
import sisicotren.model.simulator.SimulationTimeOptions;
import sisicotren.ui.simulator.Point;
import sisicotren.ui.simulator.QueueBasicImpl;
import sisicotren.ui.simulator.ScreenElement;
import sisicotren.util.Log;

import java.util.Observer;
import javax.swing.JSlider;

public class MainWindow implements Observer {

	enum SimulationState {
		INIT,
		RUNNING,
		PAUSED,
		FINISHED
	}
	
	private JFrame jFrame = null; //  @jve:decl-index=0:visual-constraint="33,23"
	private JPanel jContentPane = null;
	private JMenuBar jJMenuBar = null;
	private JMenu archivoMenu = null;
	private JMenu helpMenu = null;
	private JMenuItem loadConfJMenuItem = null;
	private JMenuItem verConfigMenuItem = null;
	private JMenuItem saveConfMenuItem = null;
	private JMenuItem exitMenuItem = null;
	private JMenuItem acercaDeMenuItem = null;
	private JPanel jPanel = null;
	private JLabel relojLabel = null;
	private JLabel relojResultLabel = null;
	private JPanel jPanel1 = null;
	private JPanel jPanel2 = null;
	private JLabel clientesInjectadosLabel = null;
	private JLabel clientesInjectadosResultLabel = null;
	private JLabel clientesSistemaLabel = null;
	private JLabel clientesSistemaResultLabel = null;
	private JLabel clientesSalientesLabel = null;
	private JLabel clientesSalientesResultLabel = null;
	private JPanel jPanel3 = null;
	private JButton startSimulationButton = null;
	private JButton pauseSimulationButton = null;
	private JButton stopSimulationButton = null;
	private JButton openReportButton = null;
	private Engine engine = null;
	private Double lambda = 0.00;  //  @jve:decl-index=0:
	private Double mu = 0.00;  //  @jve:decl-index=0:
	private Double availChannelsWidth = 0.00;  //  @jve:decl-index=0:
	private Double channelWidth = 0.00;  //  @jve:decl-index=0:
	private Double interChannelSpace = 0.00;  //  @jve:decl-index=0:
	private Integer queues = 0;  //  @jve:decl-index=0:
	private Integer channels = 0;  //  @jve:decl-index=0:
	private MainWindow thiz = null;
	private EngineThread engineTh;
	private JLabel jLabelLambda;
	private JLabel jLabelMu;
	private SimulatorActionPanel simulationPanel = null;
	private ClockPainter clockPainter = null;
	private SimulationState state = null;  //  @jve:decl-index=0:
	private JPanel simulationMainPanel;
	private JMenuItem irMenuPrincipalMenuItem = null;
	private ArrayList<SimulationStats> multipleRunsStats = null;  //  @jve:decl-index=0:
	private Integer maxToleranceInQueue;  //  @jve:decl-index=0:
	private Integer maxChannelsForOpt;
	private Integer poblationSizeForOpt;
	private JSlider jSlider = null;
	private SimulationTimeOptions speedOfSimulation = SimulationTimeOptions.REAL_TIME;  //  @jve:decl-index=0:
	private Long timeOfSimulation = null;  //  @jve:decl-index=0:
	private Boolean smallIcons = false;  //  @jve:decl-index=0:
	
	public MainWindow(Boolean smallIcons) {
		this.thiz = this;
		this.smallIcons = smallIcons;
		this.multipleRunsStats = new ArrayList<SimulationStats>();
		this.getJFrame();
		InputStream is = this.getClass().getResourceAsStream("/conf/cfg.default.properties");
		if(is != null) {
			this.loadConfiguration(is);
		} else {
			Log.print("MainWindow, Configuration not found!. Loading defaults values.");
			this.setDefaultsConfigValues();
		}
		this.state = SimulationState.INIT;
	}
	
	public Boolean getUseSmallIcons() {
		return this.smallIcons;
	}
	
	private void setDefaultsConfigValues() {
		this.setLambda(25.00);
		this.setMu(28.00);
		this.setAvailableChannelsWidth(50.00);
		this.setChannelWidth(0.70);
		this.setInterChannelSpace(1.00);
		this.setMaxChannelsForOptimization(12);
		this.setMaxToleranceInQueue(10);
		this.setPoblationSize(200);
	}
	private void loadConfiguration(InputStream config) {
		Properties props = new Properties();
		// TODO : levantar la ultima configuracion utilizada.
		try {
			props.load(config);
			this.setLambda(Double.parseDouble(props.getProperty("lambda", "25.00")));
			this.setMu(Double.parseDouble(props.getProperty("mu","28.00")));
			this.setAvailableChannelsWidth(Double.parseDouble(props.getProperty("sup_total","50.00")));
			this.setChannelWidth(Double.parseDouble(props.getProperty("ancho_canal","0.7")));
			this.setInterChannelSpace(Double.parseDouble(props.getProperty("espacio_intercanal","1")));
			this.setMaxChannelsForOptimization(Integer.parseInt(props.getProperty("max_channels","12")));
			this.setMaxToleranceInQueue(Integer.parseInt(props.getProperty("max_queue_tolerance","10")));
			this.setPoblationSize(Integer.parseInt(props.getProperty("poblation_size_for_opt","200")));
		}
		catch(IOException ioExc) {
			ioExc.printStackTrace();
		}
		catch(Exception exc) {
			exc.printStackTrace();
		}
	}
	
	private void saveConfiguration(File file) {
		Properties props = new Properties();
		try {
			props.setProperty("lambda", ""+this.getLambda());
			props.setProperty("mu", ""+this.getMu());
			props.setProperty("sup_total", ""+this.getAvailableChannelsWidth());
			props.setProperty("ancho_canal", ""+this.getChannelWidth());
			props.setProperty("espacio_intercanal", ""+this.getInterChannelSpace());
			props.setProperty("max_channels", ""+this.getMaxChannelsForOptimization());
			props.setProperty("max_queue_tolerance", ""+this.getMaxToleranceInQueue());
			props.setProperty("poblation_size_for_opt", ""+this.getPoblationSize());
			props.store(new FileOutputStream(file), "");
		}
		catch(IOException ioExc) {
			ioExc.printStackTrace();
		}
		catch(Exception exc) {
			exc.printStackTrace();
		}
	}
	
	public void setChannelWidth(Double width) {
		this.channelWidth = width;
	}
	
	public Double getChannelWidth() {
		return this.channelWidth;
	}
	
	public Double getInterChannelSpace() {
		return this.interChannelSpace;
	}
	
	public void setInterChannelSpace(Double space) {
		this.interChannelSpace = space;
	}
	
	public void setAvailableChannelsWidth(Double maxWidth) {
		this.availChannelsWidth = maxWidth;
	}
	
	public Double getAvailableChannelsWidth() {
		return this.availChannelsWidth;
	}
	
	public void show() {
		this.getJFrame().setVisible(true);
	}
	
	/**
	 * This method initializes jFrame	
	 * 	
	 * @return javax.swing.JFrame	
	 */
	public JFrame getJFrame() {
		if (jFrame == null) {
			jFrame = new JFrame();
			jFrame.setSize(java.awt.Toolkit.getDefaultToolkit().getScreenSize());
			jFrame.setTitle("SiSiCoTren");
			jFrame.setJMenuBar(getJJMenuBar());
			jFrame.setContentPane(getJContentPane());
			jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		}
		return jFrame;
	}

	/**
	 * This method initializes jContentPane	
	 * 	
	 * @return javax.swing.JPanel	
	 */
	private JPanel getJContentPane() {
		if (jContentPane == null) {
			jContentPane = new JPanel();
			jContentPane.setLayout(new CardLayout());
			jContentPane.add(new WelcomePanel(this, this.getUseSmallIcons()), "WelcomeCard");
			jContentPane.add(getSimulationPanel(), "SimulationCard");
		}
		return jContentPane;
	}

	private JPanel getSimulationPanel() {
		if(simulationMainPanel == null) {
			simulationMainPanel = new JPanel();
			simulationMainPanel.setLayout(new BorderLayout());
			simulationMainPanel.add(getBottomPanel(), BorderLayout.SOUTH);
			simulationMainPanel.add(getSimulationButtonsPanel(), BorderLayout.NORTH);
			this.simulationPanel = new SimulatorActionPanel();
			this.simulationPanel.setBackground(Color.ORANGE);
			this.simulationMainPanel.add(this.simulationPanel, BorderLayout.CENTER);
			simulationMainPanel.add(getJSlider(), BorderLayout.WEST);
			simulationMainPanel.setBackground(Color.ORANGE);
		}
		return simulationMainPanel;
	}
	
	public void switchToSimulaton() {
		CardLayout cl = (CardLayout)jContentPane.getLayout();
		cl.show(jContentPane, "SimulationCard" );
	}
	
	public void switchToMainMenu() {
		CardLayout cl = (CardLayout)jContentPane.getLayout();
		cl.show(jContentPane, "WelcomeCard" );
	}
	
	/**
	 * This method initializes jJMenuBar	
	 * 	
	 * @return javax.swing.JMenuBar	
	 */
	private JMenuBar getJJMenuBar() {
		if (jJMenuBar == null) {
			jJMenuBar = new JMenuBar();
			jJMenuBar.add(getJMenu());
			jJMenuBar.add(getHelpJMenu());
		}
		return jJMenuBar;
	}

	/**
	 * This method initializes jMenu	
	 * 	
	 * @return javax.swing.JMenu	
	 */
	private JMenu getJMenu() {
		if (archivoMenu == null) {
			archivoMenu = new JMenu();
			archivoMenu.setText("Menu");
			archivoMenu.add(getIrMenuPrincipalJMenuItem());
			archivoMenu.add(getConfigurationMenuItem());
			archivoMenu.add(getSaveConfJMenuItem());
			archivoMenu.add(getLoadConfJMenuItem());
			archivoMenu.add(getExitJMenuItem());
		}
		return archivoMenu;
	}

	/**
	 * This method initializes helpJMenu	
	 * 	
	 * @return javax.swing.JMenu	
	 */
	private JMenu getHelpJMenu() {
		if (helpMenu == null) {
			helpMenu = new JMenu();
			helpMenu.setText("Ayuda");
			helpMenu.add(getAcercaDeMenuItem());
		}
		return helpMenu;
	}

	/**
	 * This method initializes loadConfJMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getLoadConfJMenuItem() {
		if (loadConfJMenuItem == null) {
			loadConfJMenuItem = new JMenuItem();
			loadConfJMenuItem.setText("Cargar Configuracion...");
			loadConfJMenuItem.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					JFileChooser fc = new JFileChooser(".", new FileSystemView(new File("")));
					int opt = fc.showOpenDialog(getJFrame());
					switch(opt) {
						case JFileChooser.APPROVE_OPTION:
							File file = fc.getSelectedFile();
							FileInputStream is = null;
							try {
								Log.print("MainWindow, Reading [" + file.getAbsolutePath() + "]");
								is = new FileInputStream(file.getAbsolutePath());
							} catch (FileNotFoundException e1) {
								Log.print("MainWindow, Error to read configuration [" + file.getAbsolutePath() + "]");
								return;
							}
							loadConfiguration(is);
							break;
						case JFileChooser.ERROR_OPTION :
							Log.print("loadConfiguration : Error");
							break;
						case JFileChooser.CANCEL_OPTION:
							Log.print("loadConfiguration : Cancel");
							break;
					}
				}
			});
		}
		return loadConfJMenuItem;
	}

	/**
	 * This method initializes newProjectJMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getConfigurationMenuItem() {
		if (verConfigMenuItem == null) {
			verConfigMenuItem = new JMenuItem();
			verConfigMenuItem.setText("Ver Configuracion...");
			verConfigMenuItem.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					ConfigurationDialog dialog = new ConfigurationDialog(thiz);
					dialog.open();
				}
			});
		}
		return verConfigMenuItem;
	}

	/**
	 * This method initializes saveConfJMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getSaveConfJMenuItem() {
		if (saveConfMenuItem == null) {
			saveConfMenuItem = new JMenuItem();
			saveConfMenuItem.setText("Grabar Configuracion...");
			saveConfMenuItem.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					JFileChooser fc = new JFileChooser(".", new FileSystemView(new File("")));
					int opt = fc.showSaveDialog(getJFrame());
					switch(opt) {
						case JFileChooser.APPROVE_OPTION:
							File file = fc.getSelectedFile();
							saveConfiguration(file);
							break;
						case JFileChooser.ERROR_OPTION :
							Log.print("SaveConfiguration : Error");
							break;
						case JFileChooser.CANCEL_OPTION:
							Log.print("SaveConfiguration : Cancel");
							break;
					}
				}
			});
		}
		return saveConfMenuItem;
	}

	/**
	 * This method initializes exitJMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getExitJMenuItem() {
		if (exitMenuItem == null) {
			exitMenuItem = new JMenuItem();
			exitMenuItem.setText("Salir");
			exitMenuItem.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					// TODO : si hay una simulacion corriendo o
					// cambios en la configuracion no salvados, abrir
					// un dialogo preguntando si se esta seguro
					// de salir.
					System.exit(0);
				}
			});
		}
		return exitMenuItem;
	}

	/**
	 * This method initializes jMenuItem4	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getAcercaDeMenuItem() {
		if (acercaDeMenuItem == null) {
			acercaDeMenuItem = new JMenuItem();
			acercaDeMenuItem.setText("Acerca de...");
		}
		return acercaDeMenuItem;
	}

	/**
	 * This method initializes jPanel	
	 * 	
	 * @return javax.swing.JPanel	
	 */
	private JPanel getBottomPanel() {
		if (jPanel == null) {
			jPanel = new JPanel();
			jPanel.setLayout(new BorderLayout());
			jPanel.add(getClockPanel(), BorderLayout.WEST);
			jPanel.add(getSimulationStaticsPanel(), BorderLayout.EAST);
			getClockPanel().setBackground(Color.ORANGE);
			jPanel.setBackground(Color.ORANGE);
			getSimulationStaticsPanel().setBackground(Color.ORANGE);
		}
		return jPanel;
	}

	/**
	 * This method initializes jPanel1	
	 * 	
	 * @return javax.swing.JPanel	
	 */
	private JPanel getClockPanel() {
		if (jPanel1 == null) {
			jPanel1 = new JPanel();
			jPanel1.setLayout(new GridBagLayout());
			relojResultLabel = new JLabel();
			relojResultLabel.setText("00:00:00");
			relojLabel = new JLabel();
			relojLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/clock.png")));
			jPanel1.add(relojLabel);
			jPanel1.add(relojResultLabel);
		}
		return jPanel1;
	}

	/**
	 * This method initializes jPanel2	
	 * 	
	 * @return javax.swing.JPanel	
	 */
	private JPanel getSimulationStaticsPanel() {
		if (jPanel2 == null) {
			clientesSalientesResultLabel = new JLabel();
			clientesSalientesResultLabel.setText("0");
			clientesSalientesLabel = new JLabel();
			clientesSalientesLabel.setToolTipText("Clientes salientes");
			clientesSalientesLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/user-outgoing-2.png")));
			clientesSistemaResultLabel = new JLabel();
			clientesSistemaResultLabel.setText("0");
			clientesSistemaLabel = new JLabel();
			clientesSistemaLabel.setToolTipText("Clientes en el Sistema");
			clientesSistemaLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/user-system-2.png")));
			clientesInjectadosResultLabel = new JLabel();
			clientesInjectadosResultLabel.setText("0");
			clientesInjectadosLabel = new JLabel();
			clientesInjectadosLabel.setToolTipText("Clientes inyectados");
			clientesInjectadosLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/user-incoming-2.png")));
			jLabelMu = new JLabel();
			jLabelMu.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/mu.png")));
			jLabelMu.setText("-");
			jLabelLambda = new JLabel();
			jLabelLambda.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/lambda.png")));
			jLabelLambda.setText("-");
			jPanel2 = new JPanel();
			jPanel2.setLayout(new GridBagLayout());
			jPanel2.add(jLabelMu, new GridBagConstraints());
			jPanel2.add(jLabelLambda, new GridBagConstraints());
			jPanel2.add(clientesInjectadosLabel, new GridBagConstraints());
			jPanel2.add(clientesInjectadosResultLabel, new GridBagConstraints());
			jPanel2.add(clientesSistemaLabel, new GridBagConstraints());
			jPanel2.add(clientesSistemaResultLabel, new GridBagConstraints());
			jPanel2.add(clientesSalientesLabel, new GridBagConstraints());
			jPanel2.add(clientesSalientesResultLabel, new GridBagConstraints());
		}
		return jPanel2;
	}

	/**
	 * This method initializes jPanel3	
	 * 	
	 * @return javax.swing.JPanel	
	 */
	private JPanel getSimulationButtonsPanel() {
		if (jPanel3 == null) {
			jPanel3 = new JPanel();
			jPanel3.setLayout(new GridBagLayout());
			jPanel3.add(getStartSimulationButton(), new GridBagConstraints());
			jPanel3.add(getPauseSimulationButton(), new GridBagConstraints());
			jPanel3.add(getStopSimulationButton(), new GridBagConstraints());
			jPanel3.add(getOpenSimulationReportButton(), new GridBagConstraints());
			
			jPanel3.setBackground(Color.ORANGE);
		}
		return jPanel3;
	}

	/**
	 * This method initializes jButton	
	 * 	
	 * @return javax.swing.JButton	
	 */
	private JButton getStartSimulationButton() {
		if (startSimulationButton == null) {
			startSimulationButton = new JButton();
			startSimulationButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/player_play.png")));
			startSimulationButton.setToolTipText("Comenzar simulacion");
			startSimulationButton.addActionListener(new java.awt.event.ActionListener() {

				public void actionPerformed(java.awt.event.ActionEvent e) {
					// si la simulacion esta en pausa, la continua 
					if(state == SimulationState.PAUSED) {
						startSimulationButton.setEnabled(false);
						pauseSimulationButton.setEnabled(true);
						stopSimulationButton.setEnabled(true);
						engine.resume();
						state = SimulationState.RUNNING;
						return;
					}
					// Sino quiere decir que se quiere iniciar la simulacion
					// por primera vez.
					clientesInjectadosResultLabel.setText("0");
					clientesSalientesResultLabel.setText("0");
					
					// Si no se calculo el óptimo el usuario debe indicar
					// la cantidad de canales a utilizar.
					if(getTotalQueues() == 0 || getTotalChannels() == 0) {
						if( ! setTotalChannelsFromDialog() )
							return;
					}
					else {
						int optSelected = JOptionPane.showConfirmDialog(thiz.jFrame, 
								"¿Desea utilizar la cantidad óptima de molinetes?",
	                            "Cantidad de Canales",
	                            JOptionPane.YES_NO_CANCEL_OPTION);
						switch(optSelected) {
							case JOptionPane.YES_OPTION :
								break;
							case JOptionPane.NO_OPTION :
								if( ! setTotalChannelsFromDialog() )
									return;
								
								break;
							default:
								return;
						}
					}
					if( ! numOfChannelsIsValid() ) {
						JOptionPane.showMessageDialog(thiz.jFrame, 
								"La cantidad de canales excede el espacio disponible.", 
								"Demasiados Canales", 
								JOptionPane.ERROR_MESSAGE);
						
						return;
					}
					
					// chequeo que Rho sea menor a 1.
					if(getLambda() / (getMu() * getTotalChannels()) > 1) {
						if(JOptionPane.showConfirmDialog(
								thiz.jFrame, 
								"La tasa de arribos supera la capacidad de atención de los canales. ¿Desea continuar de todos modos?", 
								"Capacidad de los canales es insuficiente.", 
								JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
							return;
						}
					}
					
					initSimulation();
					state = SimulationState.RUNNING;
					startSimulationButton.setEnabled(false);
					// solo si es la primer corrida se puede pausar
					// o detener la simulacion.
					if(timeOfSimulation == null) {
						pauseSimulationButton.setEnabled(true);
						stopSimulationButton.setEnabled(true);
					}
					openReportButton.setEnabled(false);
					getJSlider().setEnabled(false);
					getJMenu().setEnabled(false);
					getHelpJMenu().setEnabled(false);
					clockPainter = new ClockPainter(relojResultLabel, speedOfSimulation);
					new Thread(clockPainter).start();
					simulationPanel.repaint();
				}
			});
		}
		return startSimulationButton;
	}
	
	private boolean numOfChannelsIsValid() {
		return this.getTotalChannels() * 
			(this.getChannelWidth() + this.getInterChannelSpace()) 
				<= this.getAvailableChannelsWidth();
	}
	
	private Boolean setTotalChannelsFromDialog() {
		String input = JOptionPane.showInputDialog(thiz.jFrame, 
				"Indique la cantidad de molinetes", 
				"Cantidad de Canales", 
				JOptionPane.INFORMATION_MESSAGE);
		try {
			int canales = Integer.parseInt(input);
			thiz.setTotalChannels(canales);
			thiz.setTotalQueues(canales);
		}
		catch(NumberFormatException exc) {
			JOptionPane.showMessageDialog(thiz.jFrame, 
					"Se ingreso un numero invalido de canales. Debe ser un número entero.", 
					"Canales invalidos",
					JOptionPane.ERROR_MESSAGE);
			
			return false;
		}
		
		return true;
	}
	
	private void endSimulation() {
		deleteChannelObservers(this.engine);
		deleteQueueObservers(this.engine);
		simulationPanel.eraseChannels();
		simulationPanel.eraseQueues();
		this.setTotalChannels(0);
		this.setTotalQueues(0);
		this.engine = null;
		System.gc();
	}
	
	private void initSimulation() {
		buildEngine();
		
		List<ScreenElement> channelObservers = addChannelObservers(engine);
		List<ScreenElement> queueObservers = addQueueObservers(engine, this);
		
		// molinetes.
		distributeElementsInScreen(channelObservers, Toolkit.getDefaultToolkit().getScreenSize(), 200);
		this.simulationPanel.setChannels(channelObservers);
		// colas.
		distributeElementsInScreen(queueObservers, Toolkit.getDefaultToolkit().getScreenSize(), 360);
		this.simulationPanel.setQueues(queueObservers);
		
		// agrego como observador al frame para poder actualizar
		// los labels con la cantidad de clientes en el sistema,
		// injectados y ejectados del sistema.
		this.engine.getInjector().addObserver(this);
		
		List<ClientChannelDispatcher> chDisp = this.engine.getChannelsDispatchers();
		Iterator<ClientChannelDispatcher> it = chDisp.iterator();
		
		while(it.hasNext()) {
			it.next().addObserver(this);
		}
		// actualizo los labels
		this.jLabelLambda.setText(""+ this.getLambda() + " ");
		this.jLabelMu.setText(""+ this.getMu() + " ");
		
		this.speedOfSimulation = getSimulationTimeOptions(this.jSlider);
		this.engine.setTime(speedOfSimulation);
		this.engine.reSynchronizeTime();
		// si ya se realizo una corrida, las subsiguientes
		// duraran el mismo tiempo que la primera.
		if(this.timeOfSimulation == null)
			engineTh = new EngineThread(engine);
		else {
			long seconds = (long)((this.speedOfSimulation.time()/60.00 * this.timeOfSimulation) / 1000) ;
			engineTh = new EngineThread(engine, seconds);
		}
		
		engineTh.addObserver(this);
		Thread th = new Thread(engineTh);
		th.start();
	}
	
	private static SimulationTimeOptions getSimulationTimeOptions(JSlider slider) {
		
		SimulationTimeOptions time = SimulationTimeOptions.FAST;
		
		if(slider.getValue() == SimulationTimeOptions.REAL_TIME.ordinal())
			time = SimulationTimeOptions.REAL_TIME;
		
		if(slider.getValue() == SimulationTimeOptions.SLOW.ordinal())
			time = SimulationTimeOptions.SLOW;
		
		return time;
	}
	
	private void buildEngine() {
		if(this.engine != null) {
			this.engine = null;
			System.gc();
		}
		this.engine = new Engine(getLambda(), getMu(), getTotalQueues(), getTotalChannels());
	}
	
	private static void distributeElementsInScreen(List<ScreenElement> list, Dimension screenSize, int bottomScreen) {
		Iterator<ScreenElement> itViewCh = list.iterator();
		int totalChannels = list.size();
		// divido el ancho en franjas en donde iran los canales.
		double limitSup = screenSize.getWidth() / totalChannels;
		double limitInf = 0;

		while(itViewCh.hasNext()) {
			ScreenElement ch = itViewCh.next();
			//System.out.println("limInf[" + limitInf + "] - limSup[" + limitSup + "]");
			int x = (int)(limitInf + ((limitSup - limitInf)/2) - (ch.getWidth()/2));
			int y = (int)(screenSize.getHeight() - ch.getHeight()) - bottomScreen;
			//System.out.println("ChannelPos >> x[" + x + "] - y[" + y + "]");
			ch.setPosition(new Point(x,y));
			double newLimitSup = limitSup - limitInf;
			limitInf = limitSup;
			limitSup = newLimitSup + limitInf;
		}
	}
	
	private static void deleteChannelObservers(Engine engine) {
		List<Channel> modelChannels = engine.getChannels();
		Iterator<Channel> itModelCh = modelChannels.iterator();
		while(itModelCh.hasNext()) {
			itModelCh.next().deleteObservers();
		}
	}
	
	private static List<ScreenElement> addChannelObservers(Engine engine) {
		List<Channel> modelChannels = engine.getChannels();
		List<ScreenElement> viewChannels = new ArrayList<ScreenElement>();
		Iterator<Channel> itModelCh = modelChannels.iterator();
		while(itModelCh.hasNext()) {
			Channel modelCh = itModelCh.next();
			sisicotren.ui.simulator.Channel viewCh = new sisicotren.ui.simulator.ChannelBasicImpl(new Point(0.00,0.00));
			modelCh.addObserver(viewCh);
			viewChannels.add(viewCh);
		}
		
		return viewChannels;
	}
	
	private static void deleteQueueObservers(Engine engine) {
		List<Queue<sisicotren.model.simulator.Client>> modelQueues = engine.getQueues();
		Iterator<Queue<sisicotren.model.simulator.Client>> itModelQueue = modelQueues.iterator();
		while(itModelQueue.hasNext()) {
			itModelQueue.next().deleteObservers();
		}
	}
	
	
	private static List<ScreenElement> addQueueObservers(Engine engine, Observer observer) {
		List<Queue<sisicotren.model.simulator.Client>> modelQueues = engine.getQueues();
		List<ScreenElement> viewQueues = new ArrayList<ScreenElement>();
		Iterator<Queue<sisicotren.model.simulator.Client>> itModelQueue = modelQueues.iterator();
		while(itModelQueue.hasNext()) {
			Queue<sisicotren.model.simulator.Client> modelQueue = itModelQueue.next();
			sisicotren.ui.simulator.QueueBasicImpl viewQueue = new sisicotren.ui.simulator.QueueBasicImpl();
			viewQueue.addObserver(observer);
			modelQueue.addObserver(viewQueue);
			viewQueues.add(viewQueue);
		}
		
		return viewQueues;
	}
	
	public Double getLambda() {
		return this.lambda;
	}
	
	public void setLambda(Double lambda) {
		this.lambda = lambda;
	}
	
	public Double getMu() {
		return this.mu;
	}
	
	public void setMu(Double mu) {
		this.mu = mu;
	}
	
	public Integer getTotalQueues() {
		return this.queues;
	}
	
	public void setTotalQueues(Integer queues) {
		this.queues = queues;
	}
	
	public Integer getTotalChannels() {
		return this.channels;
	}
	
	public void setTotalChannels(Integer channels) {
		this.channels = channels;
	}

	/**
	 * This method initializes jButton1	
	 * 	
	 * @return javax.swing.JButton	
	 */
	private JButton getPauseSimulationButton() {
		if (pauseSimulationButton == null) {
			pauseSimulationButton = new JButton();
			pauseSimulationButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/player_pause.png")));
			pauseSimulationButton.setToolTipText("Pausar simulacion");
			pauseSimulationButton.setEnabled(false);
			pauseSimulationButton.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					if(state == SimulationState.RUNNING) {
						engine.pause();
						// activo el boton de play para poder retomar
						// la simulacion
						startSimulationButton.setEnabled(true);
						pauseSimulationButton.setEnabled(false);
						stopSimulationButton.setEnabled(false);
						state = SimulationState.PAUSED;
					}
				}
			});
		}
		return pauseSimulationButton;
	}

	/**
	 * This method initializes jButton2	
	 * 	
	 * @return javax.swing.JButton	
	 */
	private JButton getStopSimulationButton() {
		if (stopSimulationButton == null) {
			stopSimulationButton = new JButton();
			stopSimulationButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/player_stop.png")));
			stopSimulationButton.setToolTipText("Terminar simulacion");
			stopSimulationButton.setEnabled(false);
			stopSimulationButton.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					if(state == SimulationState.RUNNING) {
						engine.finish();
						startSimulationButton.setEnabled(false);
						pauseSimulationButton.setEnabled(false);
						stopSimulationButton.setEnabled(false);
						state = SimulationState.FINISHED;
					}
				}
			});
		}
		return stopSimulationButton;
	}

	public void setStatusOfOpenReportButton(Boolean enable) {
		this.getOpenSimulationReportButton().setEnabled(enable);
	}
	
	/**
	 * This method initializes jButton3	
	 * 	
	 * @return javax.swing.JButton	
	 */
	private JButton getOpenSimulationReportButton() {
		if (openReportButton == null) {
			openReportButton = new JButton();
			openReportButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/text_block.png")));
			openReportButton.setToolTipText("Ver Reporte");
			openReportButton.setEnabled(false);
			openReportButton.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					JDialog result = new SimulationResult(thiz, multipleRunsStats);
					result.setVisible(true);
				}
			});
		}
		return openReportButton;
	}

	public void cleanReportStaticsList(){
		if(this.multipleRunsStats != null)
			this.multipleRunsStats.clear();
		this.timeOfSimulation = null;
	}
	
	@Override
	public void update(Observable o, Object arg) {
		// el model cambio, repinto.
		this.simulationPanel.repaint();
		
		// notificacion del fin de la simulacion
		if(o instanceof EngineThread) {
			this.state = SimulationState.FINISHED;
			this.stopSimulationButton.setEnabled(false);
			this.pauseSimulationButton.setEnabled(false);
			this.startSimulationButton.setEnabled(true);
			this.openReportButton.setEnabled(true);
			this.clockPainter.stop();
			SimulationStats finalStats = this.getSimulationStats();
			
			if(this.timeOfSimulation == null){
				this.timeOfSimulation = finalStats.getTotalSimulationTime() ;
			}
			this.multipleRunsStats.add(finalStats);
			// borro observers y hago null el engine.
			this.endSimulation();
			// limpio el fondo del escenario.
			this.simulationPanel.clear();
			// activacion de menues
			getJMenu().setEnabled(true);
			getHelpJMenu().setEnabled(true);
			getJSlider().setEnabled(true);
			// hay un problema de sincronizacion con el modelo.
			this.clientesSistemaResultLabel.setText("0");
			this.clientesSalientesResultLabel.setText("" + this.clientesInjectadosResultLabel.getText());
			// mostrar dialogo notificando el fin de la simulacion.
			int opt = JOptionPane.showConfirmDialog(thiz.jFrame, 
					"La simulacion finalizo. ¿Desea ver el reporte?",
                    "Fin de la simulacion",
                    JOptionPane.YES_NO_CANCEL_OPTION);
			
			if(opt == JOptionPane.YES_OPTION) {
				JDialog result = new SimulationResult(this, this.multipleRunsStats);
				
				result.setVisible(true);
			}
		}
		else if( ! (o instanceof QueueBasicImpl)) {
			// pueden venir novedades desde el clientDispatcher (salida)
			// o desde el ClientesInjector (arribo)
			if(o instanceof ClientChannelDispatcher) {
				Integer res = Integer.parseInt(this.clientesSalientesResultLabel.getText());
				this.clientesSalientesResultLabel.setText(""+(res+1));
			}
			else if(o instanceof ClientsInjector) {
				this.clientesInjectadosResultLabel.setText("" + (Integer)arg);
			}
			
			this.clientesSistemaResultLabel.setText("" + 
					(Integer.parseInt(this.clientesInjectadosResultLabel.getText()) -
					 Integer.parseInt(this.clientesSalientesResultLabel.getText())));
		}
	}

	private SimulationStats getSimulationStats() {
		if(this.engine == null)
			return null;
		
		return this.engine.getSimulationStats();
	}

	/**
	 * This method initializes jMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getIrMenuPrincipalJMenuItem() {
		if (irMenuPrincipalMenuItem == null) {
			irMenuPrincipalMenuItem = new JMenuItem("Menu Principal");
			irMenuPrincipalMenuItem.setToolTipText("Volver al menu principal");
			irMenuPrincipalMenuItem.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					switchToMainMenu();
				}
			});
		}
		return irMenuPrincipalMenuItem;
	}

	public Integer getMaxToleranceInQueue() {
		// TODO Auto-generated method stub
		return this.maxToleranceInQueue;
	}
	
	public void setMaxToleranceInQueue(int maxTolerance) {
		this.maxToleranceInQueue = maxTolerance;
	}

	public Integer getMaxChannelsForOptimization() {
		// TODO Auto-generated method stub
		return this.maxChannelsForOpt;
	}

	public void setMaxChannelsForOptimization(int maxChannels) {
		this.maxChannelsForOpt = maxChannels;
	}
	
	public Integer getPoblationSize() {
		// TODO Auto-generated method stub
		return this.poblationSizeForOpt;
	}
	
	public void setPoblationSize(int poblationSize) {
		this.poblationSizeForOpt = poblationSize;
	}

	/**
	 * This method initializes jSlider	
	 * 	
	 * @return javax.swing.JSlider	
	 */
	private JSlider getJSlider() {
		if (jSlider == null) {
			jSlider = new JSlider(JSlider.VERTICAL, 
					SimulationTimeOptions.SLOW.ordinal(), 
					SimulationTimeOptions.FAST.ordinal(), 
					SimulationTimeOptions.REAL_TIME.ordinal());
			jSlider.setBackground(Color.ORANGE);
			Hashtable<Integer, JLabel> ht = new Hashtable<Integer, JLabel>();
			ht.put(SimulationTimeOptions.SLOW.ordinal(), new JLabel("Lento"));
			ht.put(SimulationTimeOptions.REAL_TIME.ordinal(), new JLabel("Normal"));
			ht.put(SimulationTimeOptions.FAST.ordinal(), new JLabel("Rapido"));
			jSlider.setLabelTable(ht);
			jSlider.setPaintLabels(true);
			jSlider.setPaintTicks(true);
			jSlider.setValue(SimulationTimeOptions.FAST.ordinal());
			jSlider.addChangeListener(new javax.swing.event.ChangeListener() {
				public void stateChanged(javax.swing.event.ChangeEvent e) {
					JSlider slider = (JSlider)e.getSource();
					if(!slider.getValueIsAdjusting()) {
						speedOfSimulation = getSimulationTimeOptions(slider);
						Log.print("MainWindow, Slider value [" + slider.getValue() +"]");
					}
				}
			});
		}
		return jSlider;
	}
}
